package de.velvettear.bruteforce.kernel;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.ResultSet;
import java.util.Hashtable;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JProgressBar;
import javax.swing.JSpinner;

import de.velvettear.bruteforce.kernel.threads.ExportThread;
import de.velvettear.bruteforce.kernel.threads.WorkThread;
import de.velvettear.bruteforce.util.Log;
import de.velvettear.bruteforce.util.Timestamp;

public class WordList {

	public static Hashtable<BigInteger, String> inMemory = new Hashtable<BigInteger, String>(Statics.LIMIT_MYSQL);
	
	private static boolean loadThreadRunning;
	private static boolean fillThreadRunning;
	private static boolean infoThreadRunning;
	
	private static PrintWriter wordlistWriter; 
	
	private static BigInteger wordsCreatedInTimeFrame;
	public static BigInteger wordsCreated;
	public static BigInteger possibilities;
	public static BigDecimal average;
	public static BigDecimal remaining;
	public static int percentage;	
	public static String currentWord = null;
	
	private static int maxLength = 0;
		
	public static void fillDatabase(JFrame parent, JProgressBar progressBar, JButton button, JSpinner spinner, String hostname, String username, String password, char[] charset, String word) {
		currentWord = word;		
		maxLength = 0;
		try {
			maxLength = Integer.parseInt(spinner.getValue().toString());
		} catch (Exception e) {
			Log.error(e);
		}
		Thread fillThread = new Thread(new Runnable() {
			@Override
			public void run() {
				fillThreadRunning = true;	
				
				Database.setCredentials(hostname, username, password);
				Database.createSchema();
				Word.createTable();
				StatusReport.createTable();
				
				possibilities = BruteForce.calculatePossibilities(maxLength, charset.length, new BigInteger(getLastIndex(true)));
				if (possibilities.compareTo(new BigInteger(String.valueOf(1))) < 0) {
					button.setText("Start");
					return;
				}
				
				percentage = 0;
				
				wordsCreated = new BigInteger(String.valueOf(0));
				wordsCreatedInTimeFrame = new BigInteger(String.valueOf(0));
				
				progressBar.setValue(percentage);
				progressBar.setVisible(true);
				
				while (fillThreadRunning && wordsCreated.compareTo(possibilities) < 0) {
					Thread infoThread = new Thread(new Runnable() {
						@Override
						public void run() {		
							infoThreadRunning = true;
							try {
								while(infoThreadRunning && fillThreadRunning && wordsCreated.compareTo(possibilities) < 0) {
									long tmp_start = System.currentTimeMillis();
									Thread.sleep(Statics.SLEEP_TITLEINFO);					
									average = BruteForce.calculateAveragePerSecond(tmp_start, wordsCreatedInTimeFrame);
									remaining = BruteForce.calculcateRemainingTime(BruteForce.calculateAveragePerMillisecond(tmp_start, wordsCreatedInTimeFrame), wordsCreated, possibilities);
									parent.setTitle(Statics.NAME_APPLICATION + " | " + average + "/s" + " | " + Timestamp.format(remaining));
									wordsCreatedInTimeFrame = new BigInteger(String.valueOf(0));								
								}									
							} catch (Exception e) {
								Log.error(e);
							}
							parent.setTitle(Statics.NAME_APPLICATION);
							infoThreadRunning = false;
						}						
					});
					if (!infoThreadRunning) {
						infoThread.start();
					}
					
					wordsCreated = wordsCreated.add(new BigInteger(String.valueOf(1)));
					wordsCreatedInTimeFrame = wordsCreatedInTimeFrame.add(new BigInteger(String.valueOf(1)));
					currentWord = BruteForce.next(charset, currentWord);
					Word.insert(currentWord);
					
					int tmpPercentage = percentage;
					percentage = BruteForce.calculatePercentageDone(wordsCreated, possibilities);
					progressBar.setValue(percentage);
					if (percentage > tmpPercentage) {
						Log.trace(wordsCreated + "/" + possibilities + " (" + percentage + "%)");
					}
					
					if (!ExportThread.isRunning()) {
						ExportThread export = new ExportThread();
						export.start();
					}
				}			
				progressBar.setVisible(false);
				
				fillThreadRunning = false;
				button.setText("Start");	
				if (ExportThread.isRunning()) {
					ExportThread.kill();
				}
			}					
		});				
		fillThread.start();
	}
	
	public static void fillFile(JFrame parent, JProgressBar progressBar, JButton button, JSpinner spinner, String filePath, char[] charset, boolean overwrite, String word) {	
		currentWord = word;	
		maxLength = 0;
		try {
			maxLength = Integer.parseInt(spinner.getValue().toString());
		} catch (Exception e) {
			Log.error(e);
		}
		Thread fillThread = new Thread(new Runnable() {			
			@Override
			public void run() {
				fillThreadRunning = true;	
				Log.trace("Creating wordlist ...");
				create(filePath, overwrite);
				Log.trace("Wordlist created");
					
				Log.trace("Calculating possibilities ...");
				BigInteger possibilities = BruteForce.calculatePossibilities(maxLength, charset.length, new BigInteger(getLastIndex(false)));
				if (possibilities.compareTo(new BigInteger(String.valueOf(1))) < 0) {
					button.setText("Start");
					return;
				}
				Log.trace("Maximum possibilities: " + possibilities);
					
				Log.trace("Filling wordlist ...");
				int percentage = 0;
				long timeStart = System.currentTimeMillis();				
				String word = null;
				BigInteger wordCount = new BigInteger(String.valueOf(0));
				progressBar.setValue(percentage);
				progressBar.setVisible(true);
				while (fillThreadRunning && wordCount.compareTo(possibilities) < 0) {
					int tmpPercentage = percentage;
					wordCount = wordCount.add(new BigInteger(String.valueOf(1)));
					word = BruteForce.next(charset, word);
					WordList.add(filePath, word);
					percentage = BruteForce.calculatePercentageDone(wordCount, possibilities);
					progressBar.setValue(percentage);
					if (percentage > tmpPercentage) {
						Log.trace(wordCount + "/" + possibilities + " (" + percentage + "%)");
					}
				}			
				progressBar.setVisible(false);
				long timeStop = System.currentTimeMillis();
				Log.trace("Wordlist filled");
									
				Log.trace("Words created: " + wordCount);
				Log.trace("Time spent: " + (timeStop - timeStart) + "ms");
					
				fillThreadRunning = false;
				button.setText("Start");
			}
		});		
		fillThread.start();	
	}
	
	public static boolean add(String filePath, String word) {
		boolean wordAdded = false;
		
		File wordlist = new File(filePath);
		
		if (wordlist.exists()) {
			try {
				if (wordlistWriter == null) {
					wordlistWriter = new PrintWriter(new BufferedWriter(new FileWriter(wordlist, false)));
				}
				wordlistWriter.println(word);			
				wordAdded = true;
			} catch (Exception e) {
				Log.error(e);
			}
		}
		
		return wordAdded;
	}
	
	public static boolean create(String filePath, boolean overwrite) {
		boolean wordlistCreated = false;
		
		File wordlist = new File(filePath);
				
		if (wordlist.exists()) {
			if (overwrite) {
				wordlist.delete();
			}
		}
		
		try {
			BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(wordlist)));		
			wordlistCreated = true;
			writer.close();
		} catch (Exception e) {
			Log.error(e);
		}
		
		return wordlistCreated;
	}
		
	public static String getLastIndex(boolean fromDatabase) {
		String lastIndex = "0";
	
		if (fromDatabase) {
			try {
				Database.connect();
				
				ResultSet result = Database.query("SELECT m_nID " +
													"FROM " + Statics.TABLE_WORDS + " " + 
													"WHERE m_nID = (SELECT MAX(m_nID) " + 
																	"FROM " + Statics.TABLE_WORDS + ")");
				while (result.next()) {
					lastIndex = result.getString(1);
				}
				
				Database.disconnect();
			} catch (Exception e) {
				Log.error(e);
			}
		}
		
		return lastIndex;
	}
	
	public static String getLastWord(boolean fromDatabase) {
		String lastWord = null;	
		
		if (fromDatabase) {
			try {
				Database.connect();
				
				ResultSet result = Database.query("SELECT m_sValue " +
												  "FROM " + Statics.TABLE_WORDS + " " + 
												  "WHERE m_nID = (SELECT MAX(m_nID) " + 
												  				 "FROM " + Statics.TABLE_WORDS + ")");
				while (result != null && result.next()) {
					lastWord = result.getString(1);
				}
				
				Database.disconnect();			
			} catch (Exception e) {
				Log.error(e);
			}
		}
		
		return lastWord;
	}
	
	public static void stopFilling() {
		fillThreadRunning = false;
	}
	
	public static boolean isFilling() {
		return fillThreadRunning;
	}
			
	public static void stopLoading() {
		loadThreadRunning = false;
	}
	
	public static boolean isLoading() {
		return loadThreadRunning;
	}
	
	public static void loadDatabase() {
		Thread loadThread = new Thread(new Runnable() {			
			@Override
			public void run() {
				loadThreadRunning = true;
				try {
					BigInteger currentIndex = new BigInteger(String.valueOf(0));
						
					Database.setCredentials("localhost", "bruteforce", "bf");
					Database.connect();
						
					ResultSet result = Database.query("SELECT m_nID, m_sValue " + 
													  "FROM " + Statics.TABLE_WORDS + " " +
													  "ORDER BY m_nID " +
													  "LIMIT " + Statics.LIMIT_MYSQL);
						
					while (result.next()) {
						inMemory.put(new BigInteger(result.getString(1)), result.getString(2));
						currentIndex = new BigInteger(result.getString(1));
					}
								
					WorkThread work = new WorkThread("worker1", new File("D:\\Test\\test.zip"));
					work.start();		
					WorkThread work1 = new WorkThread("worker2", new File("D:\\Test\\test.zip"));
					work1.start();	
					WorkThread work2 = new WorkThread("worker3", new File("D:\\Test\\test.zip"));
					work2.start();	
										
					while (loadThreadRunning) {
						Thread.sleep(Statics.SLEEP_FILLHASHTABLE);
						if (inMemory.size() != Statics.LIMIT_MYSQL) {
							int missing = Statics.LIMIT_MYSQL - inMemory.size();
							
							result.close();
							result = Database.query("SELECT m_nID, m_sValue " + 
									"FROM " + Statics.TABLE_WORDS + " " +
									"WHERE m_nID > " + currentIndex + " " +
									"ORDER BY m_nID " +
									"LIMIT " + missing);
							
							while (result.next()) {
								inMemory.put(new BigInteger(result.getString(1)), result.getString(2));
								currentIndex = new BigInteger(result.getString(1));
							}							
						}
					}
					
					Database.disconnect();				
				} catch (Exception e) {
					Log.error(e);
				}
				loadThreadRunning = false;
			}
		});
			
		loadThread.start();		
	}
	
}
